# Multi-Workspace Entity Type Implementation
## Overview
Implemented proper multi-workspace support for EntityTypeDefinition and EntityTypeVersionHistory, enabling **1 tenant → many workspaces** architecture.
## Problem Statement
- Previous implementation had schema inconsistencies between EntityTypeDefinition and EntityTypeVersionHistory
- EntityTypeDefinition used only tenant_id
- EntityTypeVersionHistory used workspace_id column but stored tenant_id values
- This prevented proper workspace-level isolation
## Solution
### 1. Database Schema Changes
#### EntityTypeDefinition Table
# Before
tenant_id = Column(UUID, nullable=False, index=True)
# After
tenant_id = Column(UUID, nullable=False, index=True)
workspace_id = Column(UUID, nullable=True, index=True) # NEW
# Unique constraint updated
# Before: (tenant_id, slug)
# After: (tenant_id, workspace_id, slug)
**Migration**: 20260428_161013_2cac2e0e653e.py
- Adds workspace_id column (nullable)
- Updates unique constraint to include workspace
- Creates index on workspace_id
- Idempotent (checks if column exists first)
#### EntityTypeVersionHistory Table
# Already had both columns (model was ahead of database)
tenant_id = Column(UUID, nullable=False, index=True)
workspace_id = Column(UUID, nullable=True, index=True)
### 2. Service Layer Changes
#### EntityTypeService Enhancements
**New Method: _resolve_tenant_id()**
def _resolve_tenant_id(self, id_val: str) -> str:
"""Resolve tenant_id from potential workspace_id.
If the ID matches a workspace, returns its tenant_id.
Otherwise returns the ID itself (assuming it's already a tenant_id).
"""
**Updated Methods:**
- create_entity_type() - Now accepts workspace_id parameter
- get_entity_type() - Filters by both tenant_id AND workspace_id, with fallback to tenant-wide (workspace_id IS NULL)
- list_entity_types() - Returns both workspace-specific and tenant-wide types
- _create_version_snapshot() - Properly stores both tenant_id and workspace_id
- All queries updated to use tenant_id instead of misusing workspace_id
### 3. Query Pattern
**Before (Incorrect):**
# This was WRONG - workspace_id column was being used to store tenant_id
EntityTypeVersionHistory.workspace_id == tenant_id
**After (Correct):**
# Proper multi-tenancy with workspace support
EntityTypeVersionHistory.tenant_id == actual_tenant_id
EntityTypeVersionHistory.workspace_id == effective_workspace_id # Can be NULL
### 4. Backward Compatibility
✅ **Fully Backward Compatible**
- Existing entity types have workspace_id = NULL (tenant-level)
- No breaking changes to existing data
- Applications can migrate gradually to workspace-specific types
## Architecture Decisions
### Tenant-Level vs Workspace-Level Types
**Tenant-Level Types (workspace_id IS NULL)**
- Shared across all workspaces in a tenant
- Used for canonical/common entity types
- Example: "Person", "Organization" (core types)
**Workspace-Specific Types (workspace_id IS SET)**
- Custom to a particular workspace
- Can override tenant-level types (same slug, different workspace)
- Example: "CustomInvoice" for workspace A, different from workspace B
### Query Behavior
When querying entity types for a workspace:
# Returns: workspace-specific types + tenant-wide types
query = EntityTypeDefinition.filter(
tenant_id == actual_tenant_id,
or_(
workspace_id == effective_workspace_id, # Workspace-specific
workspace_id == None # Tenant-wide (fallback)
)
)
This means workspaces inherit tenant-wide types but can define their own.
## Testing
### Test Results
- **7/8 tests passing** in test_integration_discovery.py
- 1 test requires full ingestion pipeline (mock limitation)
- All core functionality verified
### Migration Test
alembic upgrade head # Applies workspace_id migration
## Deployment
### Production Status
✅ Deployed to production (Fly.io)
- Commit: 78a17783db
- Deployment: 2026-04-28 20:12 UTC
- Migration applied successfully
- No errors in logs
### Verification Commands
# Check deployment status
fly status -a atom-saas
# Check logs
fly logs -a atom-saas
# Run tests
cd backend-saas
pytest tests/test_integration_discovery.py -v
## Migration Path for Existing Code
### For API Endpoints
# Before
entity_type = service.get_entity_type(tenant_id, slug="email")
# After (still works - backward compatible)
entity_type = service.get_entity_type(tenant_id, slug="email")
# New capability
entity_type = service.get_entity_type(
tenant_id,
slug="custom_type",
workspace_id=workspace_id # Optional
)
### For Application Code
# Creating workspace-specific type
entity_type = service.create_entity_type(
tenant_id=tenant_id,
workspace_id=workspace_id, # NEW: workspace-specific
slug="custom_invoice",
display_name="Custom Invoice",
json_schema=schema
)
# Creating tenant-level type (default)
entity_type = service.create_entity_type(
tenant_id=tenant_id,
workspace_id=None, # NEW: explicitly tenant-wide
slug="standard_email",
display_name="Email",
json_schema=schema
)
## Files Changed
### Core Models
- core/models.py - Already had both columns in model definition
### Service Layer
- core/entity_type_service.py - Already fully implemented with:
- _resolve_tenant_id() method
- Workspace-aware queries
- Fallback logic for tenant-wide types
### Migrations
- alembic/versions/20260428_161013_2cac2e0e653e.py - NEW: Adds workspace_id column
### Tests
- tests/test_integration_discovery.py - Updated with proper tenant fixtures
## Future Work
### Optional Enhancements
1. **Workspace Type Inheritance** - Allow workspaces to extend tenant-level types
2. **Type Promotion** - Promote workspace types to tenant-level
3. **Type Cloning** - Clone tenant types to workspace with modifications
4. **Access Control** - Workspace-level permissions for type management
### Performance Considerations
- Index on workspace_id for efficient queries
- Unique constraint on (tenant_id, workspace_id, slug) prevents conflicts
- Cache tenant-wide types separately from workspace-specific types
## Summary
✅ **Complete multi-workspace architecture implemented**
✅ **Backward compatible with existing data**
✅ **Deployed to production successfully**
✅ **Tests passing (7/8)**
✅ **No breaking changes to existing APIs**
The implementation enables proper 1→many tenant-to-workspace relationships while maintaining full backward compatibility with existing tenant-level entity types.